package djinni

import java.util.ListResourceBundle


import djinni.ast._
import djinni.generatorTools._
import djinni.meta._

import scala.collection.mutable.ListBuffer

class NodeJsCppGenerator(spec: Spec, helperFiles: NodeJsHelperFilesDescriptor) extends NodeJsGenerator(spec, helperFiles) {

  override def generate(idl: Seq[TypeDecl]): Unit = {

    super.generate(idl)

    //Create file with all interfaces
    val fileName = spec.nodePackage + ".cpp"
    createFile(spec.nodeOutFolder.get, fileName, { (w: writer.IndentWriter) =>

      w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
      w.wl("// This file generated by Djinni")
      w.wl
      w.wl("#include <nan.h>")
      w.wl("#include <node.h>")
      w.wl
      var interfacesToInit = new ListBuffer[TypeDecl]()
      for (td <- idl.collect { case itd: InternTypeDecl => itd }) td.body match {
        case e: Enum =>
        case r: Record =>
        case i: Interface =>
          //First include headers
          var headerName = if (i.ext.nodeJS) idNode.ty(td.ident.name) else idNode.ty(td.ident.name) + "Cpp"
          w.wl("#include \"" + headerName + "." + spec.cppHeaderExt + "\"")
          interfacesToInit += td
      }
      w.wl
      w.wl("using namespace v8;")
      w.wl("using namespace node;")
      w.wl
      w.wl("static void initAll(Local<Object> target)").braced {
        w.wl("Nan::HandleScope scope;")
        for (td <- interfacesToInit) {
          val baseClassName = marshal.typename(td.ident, td.body)
          w.wl(s"$baseClassName::Initialize(target);")
        }

      }
      w.wl(s"NODE_MODULE(${spec.nodePackage},initAll);")
    })


    //Create file with all doc interfaces
    val docName = s"${spec.nodePackage}_doc.js"
    createFile(spec.nodeOutFolder.get, docName, { (w: writer.IndentWriter) =>

      w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
      w.wl("// This file generated by Djinni")
      w.wl
      for (td <- idl.collect { case itd: InternTypeDecl => itd }) td.body match {
        case e: Enum =>
        case r: Record =>
        case i: Interface =>
          //First declare a "class"
          writeDoc(w, td.doc)
          w.wl(s"declare class ${idNode.ty(td.ident.name)}").braced {
            //then declare all methods as "functions"
            for (m <- i.methods) {
              //Overload writeDoc if necessary
              writeDoc(w, m.doc)
              val params = m.params.map(p =>  idNode.local(p.ident) + ": " + marshal.toJSType(p.ty.resolved))
              val methodName = m.ident.name
              var methodDeclaration = s"declare function $methodName${params.mkString("(", ", ", ")")}"
              val ret = marshal.returnType(m.ret)
              if (m.ret.isDefined && ret != "void") {
                methodDeclaration = s"$methodDeclaration: ${marshal.toJSType(m.ret.get.resolved)}"
              }
              methodDeclaration = s"$methodDeclaration;"
              if (m.static) {
                methodDeclaration = s"static $methodDeclaration"
              }
              w.wl(methodDeclaration)
            }
          }
      }
    })
  }

  override def generateInterface(origin: String, ident: Ident, doc: Doc, typeParams: Seq[TypeParam], i: Interface): Unit = {

    val isNodeMode = false

    //Generate header file
    generateInterface(origin, ident, doc, typeParams, i, isNodeMode)

    //Generate implementation file
    val baseClassName = marshal.typename(ident, i)
    val cppClassName = cppMarshal.typename(ident, i)

    if (i.ext.cpp) {

      val fileName = idNode.ty(ident.name) + "Cpp.cpp"
      createFile(spec.nodeOutFolder.get, fileName, { (w: writer.IndentWriter) =>
        w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
        w.wl("// This file generated by Djinni from " + origin)
        w.wl
        val hppFileName = "#include \"" + idNode.ty(ident.name) + "Cpp." + spec.cppHeaderExt + "\""
        w.wl(hppFileName)
        w.wl("#include \"" + helperFiles.ObjectWrapperHeader + "\"")
        w.wl("#include \"" + helperFiles.HexUtilsHeader + "\"")
        w.wl
        w.wl("using namespace v8;")
        w.wl("using namespace node;")
        w.wl("using namespace std;")
        w.wl
        var factory: Option[Interface.Method] = None
        var factoryFound = false
        for (m <- i.methods) {
          val methodName = m.ident.name
          w.w(s"NAN_METHOD($baseClassName::$methodName)").braced {

            //Check if we have Callback or ListCallback as argument
            var args = ""
            var resolver = ""
            var hasCallback = false
            m.params.foreach(p => {
              val index = m.params.indexOf(p)

              args = s"${args}arg_$index"
              if (p != m.params.last) {
                args = s"${args},"
              }

              if (p.ty.expr.ident.name.contains("Callback") ||
                p.ty.expr.ident.name.contains("ListCallback")) {
                resolver = s"arg_${index}_resolver"
                hasCallback = true
              }
            })

            val argsLength = if (hasCallback) m.params.length - 1 else m.params.length
            w.wl
            w.wl("//Check if method called with right number of arguments")
            w.wl(s"if(info.Length() != $argsLength)").braced {
              val error = s""""$baseClassName::$methodName needs $argsLength arguments""""
              w.wl(s"return Nan::ThrowError($error);")
            }

            w.wl
            w.wl("//Check if parameters have correct types")
            //Retrieve all method’s parameter and test their types
            val countArgs = checkAndCastTypes(ident, i, m, w)

            //If it's static method no need to get C++ implementation
            if(!m.static) {
              w.wl
              w.wl("//Unwrap current object and retrieve its Cpp Implementation")
              w.wl(s"auto cpp_impl = djinni::js::ObjectWrapper<${spec.cppNamespace}::$cppClassName>::Unwrap(info.This());")

              //Test if implementation is null
              w.wl(s"if(!cpp_impl)").braced {
                val error = s""""$baseClassName::$methodName : implementation of $cppClassName is not valid""""
                w.wl(s"return Nan::ThrowError($error);")
              }
            }

            val cppRet = cppMarshal.returnType(m.ret)
            if (m.ret.isDefined && cppRet != "void") {

              w.wl
              if(!m.static) {
                w.wl(s"auto result = cpp_impl->$methodName($args);")
              } else {
                w.wl(s"auto result = ${spec.cppNamespace}::${idCpp.ty(ident.name)}::$methodName($args);")
              }

              w.wl
              w.wl("//Wrap result in node object")
              marshal.fromCppArgument(m.ret.get.resolved, s"arg_$countArgs", "result", w)
              w.wl
              w.wl("//Return result")
              w.wl(s"info.GetReturnValue().Set(arg_$countArgs);")
            } else {
              w.wl(s"cpp_impl->$methodName($args);")

              if (hasCallback) {
                w.wl(s"info.GetReturnValue().Set($resolver->GetPromise());")
              }

            }
          }
          //Get factory method if it exists (will be used for Nan::New method)
          if (!factoryFound && m.static) {
            factoryFound = m.ret.exists { x =>
              val returnTypeName = cppMarshal.paramType(x.resolved, true)
              returnTypeName.equals(s"std::shared_ptr<$cppClassName>")
            }
            if (factoryFound) {
              factory = Some(m)
            }
          }
        }
        w.wl
        createNanNewMethod(ident, i, factory, w)
        w.wl
        createWrapMethod(ident, i, w)
        w.wl
        createCheckMethod(ident, i, w)
        w.wl
        createInitializeMethod(ident, i, w)
      })
    }
  }

  def createCheckMethod(ident: Ident, i: Interface, wr: writer.IndentWriter): Unit = {
    val baseClassName = marshal.typename(ident, i)
    val cppClassName = cppMarshal.typename(ident, i)
    wr.w(s"NAN_METHOD($baseClassName::isNull)").braced {
      wr.wl(s"auto cpp_implementation = djinni::js::ObjectWrapper<${spec.cppNamespace}::$cppClassName>::Unwrap(info.This());")
      wr.wl("auto isNull = !cpp_implementation ? true : false;")
      wr.wl("return info.GetReturnValue().Set(Nan::New<Boolean>(isNull));")
    }
  }
}


